
package com.tools.cleanmaster.model.db;

import android.content.Context;

import com.tools.cleanmaster.CleanApplication;
import com.tools.cleanmaster.store.DatabaseUpdateSharePrefManager;
import com.tools.cleanmaster.utils.DigestEncodingUtils;
import com.tools.cleanmaster.utils.FeatureConfig;
import com.tools.cleanmaster.utils.FileHelper;
import com.tools.cleanmaster.utils.JsonParser;
import com.tools.cleanmaster.utils.LogHelper;
import com.tools.cleanmaster.utils.NetworkUtils;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

public class DatabaseUpdatePuller implements CmsPuller {
    /**
     * 保存服务器数据库配置信息
     */
    public static class DbServerConfigItem {
        public String dbId;
        public String dbUrl;
        public String dbMd5;
        public int dbFormatVersion;
        public int dbVerion;
    }

    /**
     * we use this item pull data base from server and copy database from asset.
     */
    public static abstract class DatabaseUpdatePullerItem {
        private DbServerConfigItem serverConfig;


        public abstract String getUpdateId();

        public abstract File getLocalDbPath(Context ctx);

        public abstract String getDbName();

        public abstract int getAssetDbVersion();

        public abstract int getDbVersion();

        public void setDbVersion(int dbVersion) {
            DatabaseUpdateSharePrefManager.setCurDbConfigoVersion(CleanApplication.getInstance(),
                    getUpdateId(), dbVersion);
        }

        public boolean isNeedUpgrade() {
            if (serverConfig == null) {
                return false;
            }
            return getDbVersion() < serverConfig.dbVerion;
        }

        public boolean isNeedCopyAsset() {
            final int localDataDbVersion = getDbVersion();
            final int assetDbVersion = getAssetDbVersion();
            return assetDbVersion > localDataDbVersion;
        }

        public void copyAssetDb() {
            final Context ctx = CleanApplication.getInstance();
            if (!isNeedCopyAsset()) {
                if (FeatureConfig.DEBUG_LOG) {
                    LogHelper.d(TAG, "Dot not need copy asset db!");
                }
                return;
            }

            File file = getLocalDbPath(CleanApplication.getInstance());
            File parent = file.getParentFile();

            if (parent != null && !parent.exists()) {
                parent.mkdirs();
            }
            InputStream myInput = null;
            OutputStream myOutput = null;
            try {
                if (!file.exists()) {
                    file.createNewFile();
                }

                myInput = ctx.getAssets().open(getDbName());
                myOutput = new FileOutputStream(file);

                byte[] buffer = new byte[1024];
                int i = 0;
                while ((i = myInput.read(buffer)) > 0) {
                    myOutput.write(buffer, 0, i);
                }

                myOutput.flush();
                setDbVersion(getAssetDbVersion());
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (myInput != null) {
                    try {
                        myInput.close();
                    } catch (IOException e) {
                    }
                }
                if (myOutput != null) {
                    try {
                        myOutput.close();
                    } catch (IOException e) {
                    }
                }
            }
        }

        /**
         * return database data format version. if you add a column to a
         * database, you should increase the database data version.
         *
         * @return
         */
        public abstract int getDbFormatVersion();

        public void notifyUpgraded() {
        }
    }

    private static final String SERVER_HOST;
    public static final String UPDATE_HOST;
    public static final String HTTP_API_KEY;
    public static final String HTTP_API_SECRET;

    static {
        SERVER_HOST = "";
        UPDATE_HOST = "";
        HTTP_API_KEY = "";
        HTTP_API_SECRET = "";
    }

    public final static String ANTIVIRUS_CLOUDSCAN_URL = SERVER_HOST + "/scan";

    public final static String VIRUS_DECS_URL = SERVER_HOST + "/virusinfo";

    private static final String TAG = "CommonUpdateManager";
    private static final boolean DEBUG = FeatureConfig.DEBUG_LOG;

    private static final long DATABASE_FAIL_PULLING_GAP = 1000L * 60 * 60 * 2;

    private static final String KEY_DB_ID = "type";
    private static final String KEY_DB_URL = "url";
    private static final String KEY_DB_MD5 = "md5";
    private static final String KEY_DB_FORMAT_VERSION = "dt_v";
    private static final String KEY_DB_VERSION = "db_v";
    public static final String DATA_DIR_DOWNLOAD = "db_download";

    protected Context mContext;
    private HashMap<String, DatabaseUpdatePullerItem> mPullerItems = new HashMap<String, DatabaseUpdatePullerItem>();

    public DatabaseUpdatePuller() {
        mContext = CleanApplication.getInstance();
        for (String dbId : getDBIds()) {
            mPullerItems.put(dbId, DatabaseUpdateConstants.creatDatabaseUpdateItem(dbId));
        }
    }

    protected String[] getDBIds() {
        return DatabaseUpdateConstants.DB_IDS_1;
    }

    public DatabaseUpdatePuller(HashMap<String, DatabaseUpdatePullerItem> items) {
        mContext = CleanApplication.getInstance();
        mPullerItems = items;
    }

    public void setDbVersion(String dbId, int dbVersion) {
        mPullerItems.get(dbId).setDbVersion(dbVersion);
    }

    private int getDbFormatVersion(String dbId) {
        return mPullerItems.get(dbId).getDbFormatVersion();
    }

    /**
     * 实现auth_ver的签名算法来拼接url.
     *
     * @param cxt
     * @param url
     * @param extraParams
     * @return
     * @throws IOException
     */
    public static String appendDxUrlParamsVersion2(Context cxt, String url, String extraParams)
            throws IOException {
        long curTime = System.currentTimeMillis();
        StringBuilder sb = new StringBuilder();
        sb.append(url);
        if (!url.contains("?")) {
            sb.append('?');
        } else if (!url.endsWith("?")) {
            sb.append("&");
        }
        sb.append("auth_ver=2");
        sb.append("&appkey=").append(HTTP_API_KEY);
        sb.append("&nonce=").append(curTime);
        sb.append("&").append("");
        if (extraParams != null) {
            sb.append("&");
            sb.append(extraParams);
        }
        String sign = computeHttpApiSignature(sb.toString());
        sb.append("&s=").append(sign);
        return sb.toString();
    }

    private static String computeHttpApiSignature(String url) {
        //        List<NameValuePair> allParams;
        //        try {
        //            allParams = URLEncodedUtils.parse(new URI(url), HTTP.UTF_8);
        //        } catch (URISyntaxException e) {
        //            if (DEBUG)
        //                LogHelper.w(TAG, "failed to parse url: " + url, e);
        //            throw new IOException("failed to parse url: " + url);
        //        }
        //
        //        TreeMap<String, String> sortedParams = new TreeMap<String, String>();
        //        for (NameValuePair param : allParams) {
        //            sortedParams.put(param.getName(), param.getValue());
        //        }

        StringBuilder sb = new StringBuilder();
        //        for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
        //            sb.append(entry.getKey()).append(
        //                    TextUtils.isEmpty(entry.getValue()) ? "-" : entry.getValue());
        //        }

        sb.append(HTTP_API_SECRET);
        return DigestEncodingUtils.computeMd5HexString(sb.toString());
    }

    private List<DbServerConfigItem> queryServerConfigs(String[] dbIds) throws IOException {
        int netType = NetworkUtils.getNetworkType(mContext);
        if (netType == NetworkUtils.NET_TYPE_NONE) {
            throw new IOException("no network active");
        }

        String extraParams = buildUrlExtraParams(dbIds);
        String urlWithAuth = appendDxUrlParamsVersion2(mContext, UPDATE_HOST, extraParams);
        String response = "";// TODO 获取清理相关的数据库
        if (DEBUG) {
            LogHelper.d(TAG, "checkDb: " + Arrays.toString(dbIds) + ", response: " + response);
        }

        try {
            JSONObject root = new JSONObject(response);
            if (!JsonParser.isResponseOK(root)) {
                throw new IOException("bad response: " + response);
            }

            ArrayList<DbServerConfigItem> configsList = new ArrayList<DbServerConfigItem>();
            JSONArray dataJson = root.getJSONObject("response").getJSONArray("datas");
            for (int i = 0, len = dataJson.length(); i < len; i++) {
                JSONObject dbConfig = dataJson.getJSONObject(i);
                String id = dbConfig.getString(KEY_DB_ID);
                String url = dbConfig.getString(KEY_DB_URL);
                String md5 = dbConfig.getString(KEY_DB_MD5);
                int dbFormatVersion = dbConfig.getInt(KEY_DB_FORMAT_VERSION);
                int dbVersion = dbConfig.getInt(KEY_DB_VERSION);
                if (dbFormatVersion != getDbFormatVersion(id)) {
                    if (DEBUG) {
                        LogHelper.d(TAG, "skip not matched DB format: " + id
                                + ", formatVer: " + dbFormatVersion);
                    }
                    continue;
                }

                DbServerConfigItem item = new DbServerConfigItem();
                item.dbId = id;
                item.dbUrl = url;
                item.dbMd5 = md5;
                item.dbFormatVersion = dbFormatVersion;
                item.dbVerion = dbVersion;
                configsList.add(item);
            }

            return configsList;
        } catch (JSONException e) {
            if (DEBUG) {
                LogHelper.w(TAG, "checkDb: " + Arrays.toString(dbIds) + ", bad response: "
                        + response);
            }
            throw new IOException("bad response: " + response);
        }
    }

    private static String buildUrlExtraParams(String[] ids) {
        StringBuilder sb = new StringBuilder();
        sb.append("type=");
        int lastIndex = ids.length - 1;
        for (int i = 0; i < lastIndex; i++) {
            sb.append(ids[i]).append(",");
        }
        sb.append(ids[lastIndex]);

        return sb.toString();
    }

    /**
     * @throws IOException will be thrown if failed to update DB
     */
    private boolean downloadAndUpgrade(DatabaseUpdatePullerItem pullerItem) throws IOException {
        DbServerConfigItem item = pullerItem.serverConfig;
        String databaseId = item.dbId;

        File downloadDir = mContext.getDir(DATA_DIR_DOWNLOAD, Context.MODE_PRIVATE);
        File tmpDbFile = new File(downloadDir, "db_" + databaseId);


        // download DB
        String urlWithAuth = appendDxUrlParamsVersion2(mContext, item.dbUrl, null);
        // check MD5
        byte[] md5Bytes = FileHelper.computeFileMd5(tmpDbFile);
        //String md5Hex = new String(Hex.encodeHex(md5Bytes, true));
        String md5Hex = "123";

        if (!md5Hex.equalsIgnoreCase(item.dbMd5)) {
            throw new IOException("md5 not matched: " + databaseId + ", md5: " + md5Hex);
        }

        // Replace DB
        boolean sucess = tmpDbFile.renameTo(pullerItem.getLocalDbPath(mContext));
        if (sucess) {
            // update configs
            pullerItem.setDbVersion(item.dbVerion);
            pullerItem.notifyUpgraded();
            if (DEBUG) {
                LogHelper.d(TAG, "succeeded to update db: " + databaseId
                        + ", ver: " + item.dbVerion);
            }
            return true;

        } else {
            if (DEBUG) {
                LogHelper.d(TAG, "failed to update db: " + databaseId
                        + ", ver: " + item.dbVerion);
            }
            return false;
        }
    }

    @Override
    public boolean pull() {
        boolean isSuccess = true;
        try {
            // 从服务器拉取配置文件
            List<DbServerConfigItem> configsList = queryServerConfigs(getDBIds());
            if (configsList == null) {
                return false;
            }
            for (DbServerConfigItem item : configsList) {
                String dbId = item.dbId;
                DatabaseUpdatePullerItem pullerItem = mPullerItems.get(dbId);
                pullerItem.serverConfig = item;

                // 检查是否需要升级
                if (pullerItem.isNeedUpgrade()) {
                    // Receive update.
                    try {
                        // 下载并升级需要升级的组件
                        if (!downloadAndUpgrade(pullerItem)) {
                            isSuccess = false;
                        }
                    } catch (Exception ex) {
                        if (DEBUG) {
                            LogHelper.e(TAG, "Download Failed: ", ex);
                        }
                        isSuccess = false;
                    }
                }
            }

            return isSuccess;
        } catch (Exception exception) {
            if (DEBUG) {
                LogHelper.e(TAG, "Pulled failed: ", exception);
            }

            isSuccess = false;
        }

        return isSuccess;
    }

    @Override
    public long getSuccessPullingGap() {
        return SUCCESS_PULLING_GAP;
    }

    @Override
    public long getFailPullingGap() {
        return DATABASE_FAIL_PULLING_GAP;
    }
}
